﻿//(XviD4PSP5) modded version, 2012
/*
 * Copyright (c) 2004-2006 Mike Matsnev.  All Rights Reserved.
 * 
 * $Id: VideoSink.cpp,v 1.8 2007/01/17 23:40:51 mike Exp $
 * 
 */

#include <windows.h>
typedef TCHAR* PTCHAR;
#include <streams.h>
#include <dvdmedia.h>
#include <atlcomcli.h>
#include "VideoSink.h"
#include "guids.h"

class CVideoSink;

static int  GetBPP(const BITMAPINFOHEADER& h) {
  switch (h.biCompression) {
    case MAKEFOURCC('Y','U','Y','2'): return 16;
    case MAKEFOURCC('Y','V','1','2'): return 12;
    case 0: return h.biBitCount;
  }
  return 0;
}

[uuid("2EE04A02-4AF5-43f8-B05B-5DEB66473419")]
interface IVSAllocator : public IUnknown {
  STDMETHOD(SetNextMT)(const AM_MEDIA_TYPE *pMT) = 0;
};

class CVSAllocator : public CMemAllocator, public IVSAllocator {
  CMediaType *m_nextmt;
public:
  CVSAllocator(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr) : CMemAllocator(pName, pUnk, phr), m_nextmt(NULL) { }
  ~CVSAllocator() {
    delete m_nextmt;
  }

  STDMETHOD(SetNextMT)(const AM_MEDIA_TYPE *pMT) {
    CMediaType *newMT = new CMediaType(*pMT);
    newMT = (CMediaType *)InterlockedExchangePointer((void **)&m_nextmt, newMT);
    if (newMT != NULL)
      delete pMT;
    return S_OK;
  }

  STDMETHOD(GetBuffer)(IMediaSample **ppS, REFERENCE_TIME *pStart, REFERENCE_TIME *pStop, DWORD dwFlags) {
    CMediaType *pMT = (CMediaType *)InterlockedExchangePointer((void **)&m_nextmt, NULL);
    if (pMT != NULL) {
      BITMAPINFOHEADER *bmh = NULL;

      if (pMT->formattype == FORMAT_VideoInfo)
        bmh = &((VIDEOINFOHEADER *)pMT->pbFormat)->bmiHeader;
      else if (pMT->formattype == FORMAT_VideoInfo2)
        bmh = &((VIDEOINFOHEADER2 *)pMT->pbFormat)->bmiHeader;

      if (bmh != NULL) {
        ALLOCATOR_PROPERTIES ap, act;

        Decommit();
        GetProperties(&ap);

        long  newsize = (bmh->biWidth * abs(bmh->biHeight) * GetBPP(*bmh)) >> 3;

        if (ap.cbBuffer < newsize)
          ap.cbBuffer = newsize;

        SetProperties(&ap, &act);
        Commit();
      }
    }

    HRESULT hr = CMemAllocator::GetBuffer(ppS, pStart, pStop, dwFlags);
    if (SUCCEEDED(hr) && pMT != NULL)
      (*ppS)->SetMediaType(pMT);

    if (pMT != NULL)
      delete pMT;

    return hr;
  }

  DECLARE_IUNKNOWN;
  STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv) {
    if (riid == __uuidof(IVSAllocator))
      return GetInterface((IVSAllocator *)this, ppv);

    return CMemAllocator::NonDelegatingQueryInterface(riid, ppv);
  }
};

class CVideoSinkPin : public CRenderedInputPin {
  bool        m_changedmt;
  unsigned    m_types;
  CVideoSink  *m_sink;

  HRESULT CheckMediaType(const CMediaType *pMT) {
    if (pMT->majortype != MEDIATYPE_Video ||
	(pMT->formattype != FORMAT_VideoInfo && pMT->formattype != FORMAT_VideoInfo2))
      return VFW_E_TYPE_NOT_ACCEPTED;

    if (pMT->subtype == MEDIASUBTYPE_RGB24 && m_types & IVS_RGB24)
      return S_OK;
    if (pMT->subtype == MEDIASUBTYPE_RGB32 && m_types & IVS_RGB32)
      return S_OK;
    if (pMT->subtype == MEDIASUBTYPE_YUY2 && m_types & IVS_YUY2)
      return S_OK;
    if (pMT->subtype == MEDIASUBTYPE_YV12 && m_types & IVS_YV12)
      return S_OK;

    return VFW_E_TYPE_NOT_ACCEPTED;
  }

  STDMETHOD(Receive)(IMediaSample *pS);
  STDMETHOD(EndOfStream)();
  STDMETHOD(BeginFlush)();

public:
  CVideoSinkPin(CVideoSink *sink, HRESULT *phr);

  STDMETHOD(GetAllocator)(IMemAllocator **ppAllocator) {
    CAutoLock cObjectLock(m_pLock);

    if (m_pAllocator == NULL) {
      HRESULT hr = S_OK;
      m_pAllocator = new CVSAllocator(NAME("CVSAllocator"), NULL, &hr);
      if (FAILED(hr)) {
        delete m_pAllocator;
        m_pAllocator = NULL;
        return hr;
      }
      m_pAllocator->AddRef();
    }
    ASSERT(m_pAllocator != NULL);
    *ppAllocator = m_pAllocator;
    m_pAllocator->AddRef();
    return NOERROR;
  }
  STDMETHOD(NotifyAllocator)(IMemAllocator *pAlloc, BOOL bReadOnly) {
    CAutoLock cObjectLock(m_pLock);

    CComQIPtr<IVSAllocator> pVSA(pAlloc);
    if (!pVSA)
      return E_NOINTERFACE;
    if (m_changedmt) {
      m_changedmt = false;
      pVSA->SetNextMT(&m_mt);
    }
    return CRenderedInputPin::NotifyAllocator(pAlloc, bReadOnly);
  }

  HRESULT SetMediaType(const CMediaType *pMT) {
    HRESULT hr = CRenderedInputPin::SetMediaType(pMT);
    if (FAILED(hr))
      return hr;

    unsigned  type, width, height, bpp, arx, ary;
    int       stride;
    if (FAILED(hr = GetFrameFormat(&type, &width, &height, &stride, &bpp, &arx, &ary, NULL)))
      return hr;

    if ((stride & 15) != 0) { // extend
      CMediaType newMT(m_mt);

      if (newMT.formattype == FORMAT_VideoInfo) {
        VIDEOINFOHEADER *vh = (VIDEOINFOHEADER *)newMT.pbFormat;

        vh->bmiHeader.biWidth = ((abs(stride) + 15) & ~15) / bpp;
        vh->rcTarget.left = vh->rcTarget.top = 0;
        vh->rcTarget.right = width;
        vh->rcTarget.bottom = height;
        vh->rcSource = vh->rcTarget;
      } else if (newMT.formattype == FORMAT_VideoInfo2) {
        VIDEOINFOHEADER2 *vh = (VIDEOINFOHEADER2 *)newMT.pbFormat;

        vh->bmiHeader.biWidth = ((abs(stride) + 15) & ~15) / bpp;
        vh->rcTarget.left = vh->rcTarget.top = 0;
        vh->rcTarget.right = width;
        vh->rcTarget.bottom = height;
        vh->rcSource = vh->rcTarget;
      } else
        return E_FAIL;

      hr = m_Connected->QueryAccept(&newMT);
      if (SUCCEEDED(hr)) {
        hr = CRenderedInputPin::SetMediaType(&newMT);
        if (FAILED(hr))
          return hr;

        CComQIPtr<IVSAllocator> pVSA(m_pAllocator);
        if (pVSA)
          pVSA->SetNextMT(&newMT);
        else
          m_changedmt = true;
      }
    }

    return S_OK;
  }

  BOOL            AtEOF() { return m_bAtEndOfStream; }
  REFERENCE_TIME  SegStartTime() { return m_tStart; }
  REFERENCE_TIME  SegStopTime() { return m_tStop; }
  unsigned        GetTypes() { return m_types; }
  void            SetTypes(unsigned t) { m_types = t; }

  HRESULT GetFrameFormat(unsigned *type, unsigned *width, unsigned *height, int *stride, unsigned *pbpp, unsigned *arx, unsigned *ary, __int64 *def_duration)
  {
    if (!IsConnected())
      return VFW_E_NOT_CONNECTED;

    unsigned bpp;

    if (m_mt.subtype == MEDIASUBTYPE_RGB24)
      *type = IVS_RGB24, bpp = 3;
    else if (m_mt.subtype == MEDIASUBTYPE_RGB32)
      *type = IVS_RGB32, bpp = 4;
    else if (m_mt.subtype == MEDIASUBTYPE_YUY2)
      *type = IVS_YUY2, bpp = 2;
    else if (m_mt.subtype == MEDIASUBTYPE_YV12)
      *type = IVS_YV12, bpp = 1;
    else
      return VFW_E_INVALID_MEDIA_TYPE;

    if (pbpp)
      *pbpp = bpp;

    BITMAPINFOHEADER *bmh;
    RECT             rct;

    if (m_mt.formattype == FORMAT_VideoInfo && m_mt.FormatLength() >= sizeof(VIDEOINFOHEADER)) {
      VIDEOINFOHEADER *vh = (VIDEOINFOHEADER *)m_mt.Format();
      bmh = &vh->bmiHeader;
      rct = vh->rcTarget;
      if (arx)
        *arx = 1;
      if (*ary)
        *ary = 1;
      if (def_duration)
        *def_duration = vh->AvgTimePerFrame;
    } else if (m_mt.formattype == FORMAT_VideoInfo2 && m_mt.FormatLength() >= sizeof(VIDEOINFOHEADER2)) {
      VIDEOINFOHEADER2 *vh = (VIDEOINFOHEADER2 *)m_mt.Format();
      bmh = &vh->bmiHeader;
      rct = vh->rcTarget;
      if (arx)
        *arx = vh->dwPictAspectRatioX;
      if (ary)
        *ary = vh->dwPictAspectRatioY;
      if (def_duration)
        *def_duration = vh->AvgTimePerFrame;
    } else
      return VFW_E_INVALID_MEDIA_TYPE;

    if (stride)
      *stride = (bmh->biHeight > 0 && bmh->biCompression == 0 ? -1 : 1) * (int)bmh->biWidth * (int)bpp;

    if (rct.right != 0)
      *width = rct.right - rct.left;
    else
      *width = bmh->biWidth;
    if (rct.bottom != 0)
      *height = rct.bottom - rct.top;
    else
      *height = abs(bmh->biHeight);

    return S_OK;
  }

  DECLARE_IUNKNOWN;
};

class CVideoSink :
  public CBaseFilter,
  public IVideoSink,
  public IVideoSink2,
  public IAMFilterMiscFlags
{
  CVideoSinkPin         *m_pin;
  CRendererPosPassThru  *m_rpp;
  CCritSec              m_lock;

  int	    GetPinCount() { return 1; }
  CBasePin  *GetPin(int n) { return n == 0 ? m_pin : NULL; }

  CComPtr<IMediaSample>      m_sample;
  HANDLE                     m_hEvent1, m_hEvent2, m_hNotify;
  CComPtr<IVideoSinkNotify>  m_notify;
  REFERENCE_TIME             m_tc_offset;
  REFERENCE_TIME             m_tc_duration;

public:
  CVideoSink(IUnknown *pUnk, HRESULT *phr) :
    CBaseFilter(_T("CVideoSink"), pUnk, &m_lock, CLSID_AegiVideoSink),
    m_tc_duration(-1),
    m_tc_offset(0),
    m_pin(NULL),
    m_rpp(NULL)
  {
    m_pin = new CVideoSinkPin(this, phr);
    if (FAILED(*phr))
      return;
    m_rpp = new CRendererPosPassThru(NAME("CVideoSink PosPassThru"), CBaseFilter::GetOwner(), phr, m_pin);
    if (FAILED(*phr))
      return;

    m_hEvent1 = CreateEvent(NULL, FALSE, FALSE, NULL);
    m_hEvent2 = CreateEvent(NULL, FALSE, FALSE, NULL);
    m_hNotify = NULL;
  }
  ~CVideoSink() {
    m_sample = NULL;
    delete m_rpp;
    delete m_pin;
    CloseHandle(m_hEvent1);
    CloseHandle(m_hEvent2);
  }

  CCritSec *pStateLock() { return m_pLock; }

  // called when lock is held
  HRESULT Receive(IMediaSample *pS)
  {
    if (pS == NULL)
      m_rpp->EOS();
    else
      m_rpp->RegisterMediaTime(pS);

	if (m_tc_duration < 0) //Один раз
	{
		REFERENCE_TIME rtS, rtE;
		if (SUCCEEDED(pS->GetTime(&rtS, &rtE)))
		{
			m_tc_duration = max(rtE - rtS, 0);
			if (m_tc_offset < 0)
				m_tc_offset = max(rtS + m_pin->SegStartTime(), 0);
		}
		else
		{
			m_tc_duration = 0;
			if (m_tc_offset < 0)
				m_tc_offset = 0;
		}
	}

    // notify callback
    CComPtr<IVideoSinkNotify> notify = m_notify;
    HANDLE hNotify = m_hNotify;

    if (notify || hNotify)
    {
      // save our sample
      m_sample = pS;

      // notify receiver
      SetEvent(m_hEvent1);
    }

    pStateLock()->Unlock();

    if (notify || hNotify)
    {
      if (notify) notify->FrameReady();
      if (hNotify) SetEvent(hNotify);

      // wait until the thing is processed
      if (pS != NULL)
        WaitForSingleObject(m_hEvent2, INFINITE);
    }

    if (pS == NULL)
      NotifyEvent(EC_COMPLETE, 0, (LONG_PTR)static_cast<IBaseFilter*>(this));

    return S_OK;
  }

  STDMETHOD(SetTCOffset)(REFERENCE_TIME offset) {
    CAutoLock lock(pStateLock());
    m_tc_offset = offset;
    return S_OK;
  }

  STDMETHOD(GetTCOffset)(REFERENCE_TIME *offset, REFERENCE_TIME *duration) {
    CAutoLock lock(pStateLock());
    if (m_tc_duration < 0) return E_FAIL; //Граф не запускался или IMediaSample не пришел
    if (offset) *offset = m_tc_offset;
    if (duration) *duration = m_tc_duration;
    return S_OK;
  }

  HRESULT BeginFlush() {
    CAutoLock lock(pStateLock());
    ResetEvent(m_hEvent1);
    m_sample = NULL;
    SetEvent(m_hEvent2);
    return S_OK;
  }

  STDMETHOD(Reset)() {
    //CAutoLock lock(pStateLock());
    BeginFlush();
    //SetEvent(m_hEvent1); //Ждем в ReadFrame(), когда кадр будет получен в Receive() (т.е. нам есть, что считывать)
    //SetEvent(m_hEvent2); //В Receive() ждем, когда кадр будет считан в ReadFrame() или сброшен в BeginFlush() (т.е. обработан)
    //SetEvent(evtDoneWithSample); //DSS
    //SetEvent(evtNewSampleReady); //DSS
    return S_OK;
  }

  STDMETHOD(Stop)() {
    BeginFlush();
    return CBaseFilter::Stop();
  }

  // IVideoSink
  STDMETHOD(SetAllowedTypes)(unsigned types) {
    CAutoLock lock(pStateLock());
    m_pin->SetTypes(types);
    return S_OK;
  }
  STDMETHOD(GetAllowedTypes)(unsigned *types) {
    CheckPointer(types, E_POINTER);
    CAutoLock lock(pStateLock());
    *types = m_pin->GetTypes();
    return S_OK;
  }
  STDMETHOD(NotifyFrame)(IVideoSinkNotify *notify) {
    CAutoLock lock(pStateLock());
    m_notify = notify;
    return S_OK;
  }
  STDMETHOD(GetFrameFormat)(unsigned *type, unsigned *width, unsigned *height, unsigned *arx, unsigned *ary) {
    CheckPointer(type, E_POINTER);
    CheckPointer(width, E_POINTER);
    CheckPointer(height, E_POINTER);
    CAutoLock lock(pStateLock());
    return m_pin->GetFrameFormat(type, width, height, NULL, NULL, arx, ary, NULL);
  }
  STDMETHOD(ReadFrame)(ReadFrameFunc f, void *arg)
  {
    {
      CAutoLock	lock(pStateLock());
      if (m_pin->AtEOF())
      {
        if (WaitForSingleObject(m_hEvent1, 0) == WAIT_OBJECT_0)
          SetEvent(m_hEvent2);

        HANDLE hNotify = m_hNotify;
        if (hNotify) SetEvent(hNotify);

        return S_FALSE;
      }
    }

    WaitForSingleObject(m_hEvent1, INFINITE);

    HRESULT hr = S_OK;
    {
      CAutoLock	lock(pStateLock());

      CComPtr<IMediaSample> pS(m_sample);
      m_sample = NULL;

      if (!pS)
        hr = S_FALSE;
      else
      {
        REFERENCE_TIME	rtS, rtE;
        if (SUCCEEDED(pS->GetTime(&rtS, &rtE)))
          rtS += m_pin->SegStartTime() - m_tc_offset;
        else
          rtS = -1;

        if (f)
        {
          int srcS;
          BYTE *srcP;
          unsigned type, srcW, srcH, arx, ary, srcBPP;
          if (FAILED(m_pin->GetFrameFormat(&type, &srcW, &srcH, &srcS, &srcBPP, &arx, &ary, NULL)) || FAILED(pS->GetPointer(&srcP)))
            hr = E_FAIL;
          else
          {
            if (srcS < 0) srcP += abs(srcS) * (srcH - 1);
            f(rtS, type, srcBPP, srcP, srcW, srcH, srcS, arx, ary, arg);
          }
        }
      }
    }

    SetEvent(m_hEvent2);

    return hr;
  }

  // IVideoSink2
  STDMETHOD(NotifyFrame)(HANDLE hEvent) {
    m_hNotify = hEvent;
    return S_OK;
  }
  STDMETHOD(GetFrameFormat)(unsigned *type, unsigned *width, unsigned *height, unsigned *arx, unsigned *ary, __int64 *def_duration) {
    CheckPointer(type, E_POINTER);
    CheckPointer(width, E_POINTER);
    CheckPointer(height, E_POINTER);
    CAutoLock lock(pStateLock());
    return m_pin->GetFrameFormat(type, width, height, NULL, NULL, arx, ary, def_duration);
  }

  // IAMFilterMiscFlags
  STDMETHOD_(ULONG, GetMiscFlags)() { return AM_FILTER_MISC_FLAGS_IS_RENDERER; }

  // COM
  DECLARE_IUNKNOWN;
  STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv) {
    CAutoLock lock(pStateLock());

    if (riid == __uuidof(IVideoSink))
      return GetInterface((IVideoSink *)this, ppv);
    if (riid == __uuidof(IVideoSink2))
      return GetInterface((IVideoSink2 *)this, ppv);
    if (riid == __uuidof(IAMFilterMiscFlags))
      return GetInterface((IAMFilterMiscFlags *)this, ppv);

    if (riid == IID_IMediaSeeking || riid == IID_IMediaPosition)
      return m_rpp->NonDelegatingQueryInterface(riid, ppv);

    return CBaseFilter::NonDelegatingQueryInterface(riid, ppv);
  }
};

CVideoSinkPin::CVideoSinkPin(CVideoSink *sink, HRESULT *phr) :
  CRenderedInputPin(_T("CVideoSinkPin"), sink, sink->pStateLock(), phr, L"Input"),
  m_types(IVS_RGB32),
  m_sink(sink),
  m_changedmt(false)
{
}

HRESULT CVideoSinkPin::Receive(IMediaSample *pS) {
  m_pLock->Lock();

  if (m_bFlushing) {
    m_pLock->Unlock();
    return S_FALSE;
  }

  CMediaType	  MT;
  AM_MEDIA_TYPE *pMT;
  if (SUCCEEDED(pS->GetMediaType(&pMT)) && pMT != NULL) {
    MT.Set(*pMT); DeleteMediaType(pMT);

    HRESULT hr = CheckMediaType(&MT);
    if (FAILED(hr)) {
      m_pLock->Unlock();
      return hr;
    }

    SetMediaType(&MT);
  }

  if (pS->IsPreroll() == S_OK) {
    m_pLock->Unlock();
    return S_OK;
  }

  return m_sink->Receive(pS);
}

HRESULT	CVideoSinkPin::EndOfStream() {
  HRESULT hr1, hr2;

  m_pLock->Lock();
  hr1 = CRenderedInputPin::EndOfStream();
  if (m_bFlushing) {
    m_pLock->Unlock();
    return hr1;
  }

  hr2 = m_sink->Receive(NULL);
  if (FAILED(hr1))
    return hr1;
  return hr2;
}

HRESULT	CVideoSinkPin::BeginFlush() {
  HRESULT hr = CRenderedInputPin::BeginFlush();
  m_sink->BeginFlush();
  return hr;
}

//CUnknown * WINAPI CreateVideoSink(IUnknown *pUnk, HRESULT *phr) {
//  CVideoSink  *vs = new CVideoSink(pUnk, phr);
//  if (vs == NULL)
//    *phr = E_OUTOFMEMORY;
//  else if (FAILED(*phr)) {
//    delete vs;
//    vs = NULL;
//  }
//  return vs;
//}

HRESULT CreateVideoSink(IBaseFilter **pVS) {
	HRESULT hr = S_OK;
	CVideoSink *vs = new CVideoSink(NULL,&hr);
	if (vs == NULL) hr = E_OUTOFMEMORY;
	else if (FAILED(hr)) {
		delete vs;
		vs = NULL;
	}
	vs->AddRef();
	*pVS = vs;
	return hr;
}
